home *** CD-ROM | disk | FTP | other *** search
/ PC Advisor 2010 April / PCA177.iso / ESSENTIALS / Firefox Setup.exe / nonlocalized / components / storage-Legacy.js < prev    next >
Encoding:
Text File  |  2009-07-15  |  51.6 KB  |  1,479 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is mozilla.org code.
  15.  *
  16.  * The Initial Developer of the Original Code is Mozilla Corporation.
  17.  * Portions created by the Initial Developer are Copyright (C) 2007
  18.  * the Initial Developer. All Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *  Justin Dolske <dolske@mozilla.com> (original author)
  22.  *
  23.  * Alternatively, the contents of this file may be used under the terms of
  24.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  25.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  26.  * in which case the provisions of the GPL or the LGPL are applicable instead
  27.  * of those above. If you wish to allow use of your version of this file only
  28.  * under the terms of either the GPL or the LGPL, and not to allow others to
  29.  * use your version of this file under the terms of the MPL, indicate your
  30.  * decision by deleting the provisions above and replace them with the notice
  31.  * and other provisions required by the GPL or the LGPL. If you do not delete
  32.  * the provisions above, a recipient may use your version of this file under
  33.  * the terms of any one of the MPL, the GPL or the LGPL.
  34.  *
  35.  * ***** END LICENSE BLOCK ***** */
  36.  
  37.  
  38. const Cc = Components.classes;
  39. const Ci = Components.interfaces;
  40.  
  41. Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
  42.  
  43. function LoginManagerStorage_legacy() { };
  44.  
  45. LoginManagerStorage_legacy.prototype = {
  46.  
  47.     classDescription  : "LoginManagerStorage_legacy",
  48.     contractID : "@mozilla.org/login-manager/storage/legacy;1",
  49.     classID : Components.ID("{e09e4ca6-276b-4bb4-8b71-0635a3a2a007}"),
  50.     QueryInterface : XPCOMUtils.generateQI([Ci.nsILoginManagerStorage,
  51.                                     Ci.nsILoginManagerIEMigrationHelper]),
  52.  
  53.     __logService : null, // Console logging service, used for debugging.
  54.     get _logService() {
  55.         if (!this.__logService)
  56.             this.__logService = Cc["@mozilla.org/consoleservice;1"].
  57.                                 getService(Ci.nsIConsoleService);
  58.         return this.__logService;
  59.     },
  60.  
  61.     __ioService: null, // IO service for string -> nsIURI conversion
  62.     get _ioService() {
  63.         if (!this.__ioService)
  64.             this.__ioService = Cc["@mozilla.org/network/io-service;1"].
  65.                                getService(Ci.nsIIOService);
  66.         return this.__ioService;
  67.     },
  68.  
  69.     __decoderRing : null,  // nsSecretDecoderRing service
  70.     get _decoderRing() {
  71.         if (!this.__decoderRing)
  72.             this.__decoderRing = Cc["@mozilla.org/security/sdr;1"].
  73.                                  getService(Ci.nsISecretDecoderRing);
  74.         return this.__decoderRing;
  75.     },
  76.  
  77.     __utfConverter : null, // UCS2 <--> UTF8 string conversion
  78.     get _utfConverter() {
  79.         if (!this.__utfConverter) {
  80.             this.__utfConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
  81.                                   createInstance(Ci.nsIScriptableUnicodeConverter);
  82.             this.__utfConverter.charset = "UTF-8";
  83.         }
  84.         return this.__utfConverter;
  85.     },
  86.  
  87.     _utfConverterReset : function() {
  88.         this.__utfConverter = null;
  89.     },
  90.  
  91.     __profileDir: null,  // nsIFile for the user's profile dir
  92.     get _profileDir() {
  93.         if (!this.__profileDir) {
  94.             var dirService = Cc["@mozilla.org/file/directory_service;1"].
  95.                              getService(Ci.nsIProperties);
  96.             this.__profileDir = dirService.get("ProfD", Ci.nsIFile);
  97.         }
  98.         return this.__profileDir;
  99.     },
  100.  
  101.     __nsLoginInfo: null,  // Constructor for nsILoginInfo implementation
  102.     get _nsLoginInfo() {
  103.         if (!this.__nsLoginInfo)
  104.             this.__nsLoginInfo = new Components.Constructor(
  105.                 "@mozilla.org/login-manager/loginInfo;1", Ci.nsILoginInfo);
  106.         return this.__nsLoginInfo;
  107.     },
  108.  
  109.     _prefBranch : null,  // Preferences service
  110.  
  111.     _signonsFile : null,  // nsIFile for "signons3.txt" (or whatever pref is)
  112.     _debug       : false, // mirrors signon.debug
  113.  
  114.     /*
  115.      * A list of prefs that have been used to specify the filename for storing
  116.      * logins. (We've used a number over time due to compatibility issues.)
  117.      * This list is also used by _removeOldSignonsFile() to clean up old files.
  118.      */
  119.     _filenamePrefs : ["SignonFileName3", "SignonFileName2", "SignonFileName"],
  120.  
  121.     /*
  122.      * Core datastructures
  123.      *
  124.      * EG: _logins["http://site.com"][0].password
  125.      * EG: _disabledHosts["never.site.com"]
  126.      */
  127.     _logins        : null, 
  128.     _disabledHosts : null,
  129.  
  130.  
  131.     /*
  132.      * log
  133.      *
  134.      * Internal function for logging debug messages to the Error Console.
  135.      */
  136.     log : function (message) {
  137.         if (!this._debug)
  138.             return;
  139.         dump("PwMgr Storage: " + message + "\n");
  140.         this._logService.logStringMessage("PwMgr Storage: " + message);
  141.     },
  142.  
  143.  
  144.  
  145.  
  146.     /* ==================== Public Methods ==================== */
  147.  
  148.  
  149.  
  150.  
  151.     initWithFile : function(aInputFile, aOutputFile) {
  152.         this._signonsFile = aInputFile;
  153.  
  154.         this.init();
  155.  
  156.         if (aOutputFile) {
  157.             this._signonsFile = aOutputFile;
  158.             this._writeFile();
  159.         }
  160.     },
  161.  
  162.     /*
  163.      * init
  164.      *
  165.      * Initialize this storage component and load stored passwords from disk.
  166.      */
  167.     init : function () {
  168.         this._logins  = {};
  169.         this._disabledHosts = {};
  170.  
  171.         // Connect to the correct preferences branch.
  172.         this._prefBranch = Cc["@mozilla.org/preferences-service;1"].
  173.                            getService(Ci.nsIPrefService);
  174.         this._prefBranch = this._prefBranch.getBranch("signon.");
  175.         this._prefBranch.QueryInterface(Ci.nsIPrefBranch2);
  176.  
  177.         this._debug = this._prefBranch.getBoolPref("debug");
  178.  
  179.         // Check to see if the internal PKCS#11 token has been initialized.
  180.         // If not, set a blank password.
  181.         var tokenDB = Cc["@mozilla.org/security/pk11tokendb;1"].
  182.                       getService(Ci.nsIPK11TokenDB);
  183.  
  184.         var token = tokenDB.getInternalKeyToken();
  185.         if (token.needsUserInit) {
  186.             this.log("Initializing key3.db with default blank password.");
  187.             token.initPassword("");
  188.         }
  189.  
  190.         var importFile = null;
  191.         // If initWithFile is calling us, _signonsFile is already set.
  192.         if (!this._signonsFile)
  193.             [this._signonsFile, importFile] = this._getSignonsFile();
  194.  
  195.         // If we have an import file, do a switcharoo before reading it.
  196.         if (importFile) {
  197.             this.log("Importing " + importFile.path);
  198.  
  199.             var tmp = this._signonsFile;
  200.             this._signonsFile = importFile;
  201.         }
  202.  
  203.         // Read in the stored login data.
  204.         this._readFile();
  205.  
  206.         // If we were importing, write back to the normal file.
  207.         if (importFile) {
  208.             this._signonsFile = tmp;
  209.             this._writeFile();
  210.         }
  211.     },
  212.  
  213.  
  214.     /*
  215.      * addLogin
  216.      *
  217.      */
  218.     addLogin : function (login) {
  219.         // Throws if there are bogus values.
  220.         this._checkLoginValues(login);
  221.  
  222.         // Clone the input. This ensures changes made by the caller to the
  223.         // login (after calling addLogin) do no change the login we have.
  224.         // Also, we rely on using login.wrappedJSObject, but can't rely on the
  225.         // thing provided by the caller to support that.
  226.         var clone = new this._nsLoginInfo();
  227.         clone.init(login.hostname, login.formSubmitURL, login.httpRealm,
  228.                    login.username,      login.password,
  229.                    login.usernameField, login.passwordField);
  230.         login = clone;
  231.  
  232.         var key = login.hostname;
  233.  
  234.         // If first entry for key, create an Array to hold its logins.
  235.         var rollback;
  236.         if (!this._logins[key]) {
  237.             this._logins[key] = [];
  238.             rollback = null;
  239.         } else {
  240.             rollback = this._logins[key].concat(); // clone array
  241.         }
  242.  
  243.         this._logins[key].push(login);
  244.  
  245.         var ok = this._writeFile();
  246.  
  247.         // If we failed, don't keep the added login in memory.
  248.         if (!ok) {
  249.             if (rollback)
  250.                 this._logins[key] = rollback;
  251.             else
  252.                 delete this._logins[key];
  253.  
  254.             throw "Couldn't write to file, login not added.";
  255.         }
  256.     },
  257.  
  258.  
  259.     /*
  260.      * removeLogin
  261.      *
  262.      */
  263.     removeLogin : function (login) {
  264.         var key = login.hostname;
  265.         var logins = this._logins[key];
  266.  
  267.         if (!logins)
  268.             throw "No logins found for hostname (" + key + ")";
  269.  
  270.         var rollback = this._logins[key].concat(); // clone array
  271.  
  272.         // The specified login isn't encrypted, so we need to ensure
  273.         // the logins we're comparing with are decrypted. We decrypt one entry
  274.         // at a time, lest _decryptLogins return fewer entries and screw up
  275.         // indices between the two.
  276.         for (var i = 0; i < logins.length; i++) {
  277.  
  278.             var [[decryptedLogin], userCanceled] =
  279.                         this._decryptLogins([logins[i]]);
  280.  
  281.             if (userCanceled)
  282.                 throw "User canceled master password entry, login not removed.";
  283.  
  284.             if (!decryptedLogin)
  285.                 continue;
  286.  
  287.             if (decryptedLogin.equals(login)) {
  288.                 logins.splice(i, 1); // delete that login from array.
  289.                 break;
  290.                 // Note that if there are duplicate entries, they'll
  291.                 // have to be deleted one-by-one.
  292.             }
  293.         }
  294.  
  295.         // Did we delete the last login for this host?
  296.         if (logins.length == 0)
  297.             delete this._logins[key];
  298.  
  299.         var ok = this._writeFile();
  300.  
  301.         // If we failed, don't actually remove the login.
  302.         if (!ok) {
  303.             this._logins[key] = rollback;
  304.             throw "Couldn't write to file, login not removed.";
  305.         }
  306.     },
  307.  
  308.  
  309.     /*
  310.      * modifyLogin
  311.      *
  312.      */
  313.     modifyLogin : function (oldLogin, newLogin) {
  314.         if (newLogin instanceof Ci.nsIPropertyBag)
  315.             throw "legacy modifyLogin with propertybag not implemented.";
  316.         newLogin.QueryInterface(Ci.nsILoginInfo);
  317.         // Throws if there are bogus values.
  318.         this._checkLoginValues(newLogin);
  319.  
  320.         this.removeLogin(oldLogin);
  321.         this.addLogin(newLogin);
  322.     },
  323.  
  324.  
  325.     /*
  326.      * getAllLogins
  327.      *
  328.      * Returns an array of nsAccountInfo.
  329.      */
  330.     getAllLogins : function (count) {
  331.         var result = [], userCanceled;
  332.  
  333.         // Each entry is an array -- append the array entries to |result|.
  334.         for each (var hostLogins in this._logins) {
  335.             result = result.concat(hostLogins);
  336.         }
  337.  
  338.         // decrypt entries for caller.
  339.         [result, userCanceled] = this._decryptLogins(result);
  340.  
  341.         if (userCanceled)
  342.             throw "User canceled Master Password entry";
  343.  
  344.         count.value = result.length; // needed for XPCOM
  345.         return result;
  346.     },
  347.  
  348.  
  349.     /*
  350.      * getAllEncryptedLogins
  351.      *
  352.      * Returns an array of nsAccountInfo, each in the encrypted state.
  353.      */
  354.     getAllEncryptedLogins : function (count) {
  355.         var result = [];
  356.  
  357.         // Each entry is an array -- append the array entries to |result|.
  358.         for each (var hostLogins in this._logins) {
  359.             // Return copies to the caller. Prevents callers from modifying
  360.             // our internal storage
  361.             for each (var login in hostLogins) {
  362.                 var clone = new this._nsLoginInfo();
  363.                 clone.init(login.hostname, login.formSubmitURL, login.httpRealm,
  364.                            login.wrappedJSObject.encryptedUsername,
  365.                            login.wrappedJSObject.encryptedPassword,
  366.                            login.usernameField, login.passwordField);
  367.                 result.push(clone);
  368.             }
  369.         }
  370.  
  371.         count.value = result.length; // needed for XPCOM
  372.         return result;
  373.     },
  374.  
  375.  
  376.     /*
  377.      * searchLogins
  378.      *
  379.      * Not implemented. This interface was added to perform arbitrary searches.
  380.      * Since the legacy storage module is no longer used, there is no need to
  381.      * implement it here.
  382.      */
  383.     searchLogins : function (count, matchData) {
  384.         throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
  385.     },
  386.  
  387.  
  388.     /*
  389.      * removeAllLogins
  390.      *
  391.      * Removes all logins from storage.
  392.      */
  393.     removeAllLogins : function () {
  394.         // Delete any old, unused files.
  395.         this._removeOldSignonsFiles();
  396.  
  397.         // Disabled hosts kept, as one presumably doesn't want to erase those.
  398.         this._logins = {};
  399.         this._writeFile();
  400.     },
  401.  
  402.  
  403.     /*
  404.      * getAllDisabledHosts
  405.      *
  406.      */
  407.     getAllDisabledHosts : function (count) {
  408.         var result = [];
  409.  
  410.         for (var hostname in this._disabledHosts) {
  411.             result.push(hostname);
  412.         }
  413.  
  414.         count.value = result.length; // needed for XPCOM
  415.         return result;
  416.     },
  417.  
  418.  
  419.     /*
  420.      * getLoginSavingEnabled
  421.      *
  422.      */
  423.     getLoginSavingEnabled : function (hostname) {
  424.         return !this._disabledHosts[hostname];
  425.     },
  426.  
  427.  
  428.     /*
  429.      * setLoginSavingEnabled
  430.      *
  431.      */
  432.     setLoginSavingEnabled : function (hostname, enabled) {
  433.         // File format prohibits certain values. Also, nulls
  434.         // won't round-trip with getAllDisabledHosts().
  435.         if (hostname == "." ||
  436.             hostname.indexOf("\r") != -1 ||
  437.             hostname.indexOf("\n") != -1 ||
  438.             hostname.indexOf("\0") != -1)
  439.             throw "Invalid hostname";
  440.  
  441.         if (enabled)
  442.             delete this._disabledHosts[hostname];
  443.         else
  444.             this._disabledHosts[hostname] = true;
  445.  
  446.         this._writeFile();
  447.     },
  448.  
  449.  
  450.     /*
  451.      * findLogins
  452.      *
  453.      */
  454.     findLogins : function (count, hostname, formSubmitURL, httpRealm) {
  455.         var userCanceled;
  456.  
  457.         var logins = this._searchLogins(hostname, formSubmitURL, httpRealm);
  458.  
  459.         // Decrypt entries found for the caller.
  460.         [logins, userCanceled] = this._decryptLogins(logins);
  461.  
  462.         // We want to throw in this case, so that the Login Manager
  463.         // knows to stop processing forms on the page so the user isn't
  464.         // prompted multiple times.
  465.         if (userCanceled)
  466.             throw "User canceled Master Password entry";
  467.  
  468.         count.value = logins.length; // needed for XPCOM
  469.         return logins;
  470.     },
  471.  
  472.     
  473.     /*
  474.      * countLogins
  475.      *
  476.      */
  477.     countLogins : function (aHostname, aFormSubmitURL, aHttpRealm) {
  478.         var logins;
  479.  
  480.         // Normal case: return direct results for the specified host.
  481.         if (aHostname) {
  482.             logins = this._searchLogins(aHostname, aFormSubmitURL, aHttpRealm);
  483.             return logins.length
  484.         } 
  485.  
  486.         // For consistency with how aFormSubmitURL and aHttpRealm work
  487.         if (aHostname == null)
  488.             return 0;
  489.  
  490.         // aHostname == "", so loop through each known host to match with each.
  491.         var count = 0;
  492.         for (var hostname in this._logins) {
  493.             logins = this._searchLogins(hostname, aFormSubmitURL, aHttpRealm);
  494.             count += logins.length;
  495.         }
  496.  
  497.         return count;
  498.     },
  499.  
  500.  
  501.  
  502.  
  503.     /* ==================== Internal Methods ==================== */
  504.  
  505.  
  506.  
  507.  
  508.     /*
  509.      * _searchLogins
  510.      *
  511.      */
  512.     _searchLogins : function (hostname, formSubmitURL, httpRealm) {
  513.         var hostLogins = this._logins[hostname];
  514.         if (hostLogins == null)
  515.             return [];
  516.  
  517.         var result = [], userCanceled;
  518.  
  519.         for each (var login in hostLogins) {
  520.  
  521.             // If search arg is null, skip login unless it doesn't specify a
  522.             // httpRealm (ie, it's also null). If the search arg is an empty
  523.             // string, always match.
  524.             if (httpRealm == null) {
  525.                 if (login.httpRealm != null)
  526.                     continue;
  527.             } else if (httpRealm != "") {
  528.                 // Make sure the realms match. If search arg is null,
  529.                 // only match if login doesn't specify a realm (is null)
  530.                 if (httpRealm != login.httpRealm)
  531.                     continue;
  532.             }
  533.  
  534.             // If search arg is null, skip login unless it doesn't specify a
  535.             // action URL (ie, it's also null). If the search arg is an empty
  536.             // string, always match.
  537.             if (formSubmitURL == null) {
  538.                 if (login.formSubmitURL != null)
  539.                     continue;
  540.             } else if (formSubmitURL != "") {
  541.                 // If the stored login is blank (not null), that means the
  542.                 // login was stored before we started keeping the action
  543.                 // URL, so always match. Unless the search g
  544.                 if (login.formSubmitURL != "" &&
  545.                     formSubmitURL != login.formSubmitURL)
  546.                     continue;
  547.             }
  548.  
  549.             result.push(login);
  550.         }
  551.  
  552.         return result;
  553.     },
  554.  
  555.  
  556.     /*
  557.      * _checkLoginValues
  558.      *
  559.      * Due to the way the signons2.txt file is formatted, we need to make
  560.      * sure certain field values or characters do not cause the file to
  561.      * be parse incorrectly. Reject logins that we can't store correctly.
  562.      */
  563.     _checkLoginValues : function (aLogin) {
  564.         function badCharacterPresent(l, c) {
  565.             return ((l.formSubmitURL && l.formSubmitURL.indexOf(c) != -1) ||
  566.                     (l.httpRealm     && l.httpRealm.indexOf(c)     != -1) ||
  567.                                         l.hostname.indexOf(c)      != -1  ||
  568.                                         l.usernameField.indexOf(c) != -1  ||
  569.                                         l.passwordField.indexOf(c) != -1);
  570.         }
  571.  
  572.         // Nulls are invalid, as they don't round-trip well.
  573.         // Mostly not a formatting problem, although ".\0" can be quirky.
  574.         if (badCharacterPresent(aLogin, "\0"))
  575.             throw "login values can't contain nulls";
  576.  
  577.         // In theory these nulls should just be rolled up into the encrypted
  578.         // values, but nsISecretDecoderRing doesn't use nsStrings, so the
  579.         // nulls cause truncation. Check for them here just to avoid
  580.         // unexpected round-trip surprises.
  581.         if (aLogin.username.indexOf("\0") != -1 ||
  582.             aLogin.password.indexOf("\0") != -1)
  583.             throw "login values can't contain nulls";
  584.  
  585.         // Newlines are invalid for any field stored as plaintext.
  586.         if (badCharacterPresent(aLogin, "\r") ||
  587.             badCharacterPresent(aLogin, "\n"))
  588.             throw "login values can't contain newlines";
  589.  
  590.         // A line with just a "." can have special meaning.
  591.         if (aLogin.usernameField == "." ||
  592.             aLogin.formSubmitURL == ".")
  593.             throw "login values can't be periods";
  594.  
  595.         // A hostname with "\ \(" won't roundtrip.
  596.         // eg host="foo (", realm="bar" --> "foo ( (bar)"
  597.         // vs host="foo", realm=" (bar" --> "foo ( (bar)"
  598.         if (aLogin.hostname.indexOf(" (") != -1)
  599.             throw "bad parens in hostname";
  600.     },
  601.  
  602.  
  603.     /*
  604.      * _getSignonsFile
  605.      *
  606.      * Determines what file to use based on prefs. Returns it as a
  607.      * nsILocalFile, along with a file to import from first (if needed)
  608.      *
  609.      */
  610.     _getSignonsFile : function() {
  611.         var destFile = null, importFile = null;
  612.  
  613.         // We've used a number of prefs over time due to compatibility issues.
  614.         // Use the filename specified in the newest pref, but import from
  615.         // older files if needed.
  616.         for (var i = 0; i < this._filenamePrefs.length; i++) {
  617.             var prefname = this._filenamePrefs[i];
  618.             var filename = this._prefBranch.getCharPref(prefname);
  619.             var file = this._profileDir.clone();
  620.             file.append(filename);
  621.  
  622.             this.log("Checking file " + filename + " (" + prefname + ")");
  623.  
  624.             // First loop through, save the preferred filename.
  625.             if (!destFile)
  626.                 destFile = file;
  627.             else
  628.                 importFile = file;
  629.  
  630.             if (file.exists())
  631.                 return [destFile, importFile];
  632.         }
  633.  
  634.         // If we can't find any existing file, use the preferred file.
  635.         return [destFile, null];
  636.     },
  637.  
  638.  
  639.     /*
  640.      * _removeOldSignonsFiles
  641.      *
  642.      * Deletes any storage files that we're not using any more.
  643.      */
  644.     _removeOldSignonsFiles : function() {
  645.         // We've used a number of prefs over time due to compatibility issues.
  646.         // Skip the first entry (the newest) and delete the others.
  647.         for (var i = 1; i < this._filenamePrefs.length; i++) {
  648.             var prefname = this._filenamePrefs[i];
  649.             var filename = this._prefBranch.getCharPref(prefname);
  650.             var file = this._profileDir.clone();
  651.             file.append(filename);
  652.  
  653.             if (file.exists()) {
  654.                 this.log("Deleting old " + filename + " (" + prefname + ")");
  655.                 try {
  656.                     file.remove(false);
  657.                 } catch (e) {
  658.                     this.log("NOTICE: Couldn't delete " + filename + ": " + e);
  659.                 }
  660.             }
  661.         }
  662.     },
  663.  
  664.  
  665.     /*
  666.      * _upgrade_entry_to_2E
  667.      *
  668.      * Updates the format of an entry from 2D to 2E. Returns an array of
  669.      * logins (1 or 2), as sometimes updating an entry requires creating an
  670.      * extra login.
  671.      */
  672.     _upgrade_entry_to_2E : function (aLogin) {
  673.         var upgradedLogins = [aLogin];
  674.  
  675.         /*
  676.          * For logins stored from HTTP channels
  677.          *    - scheme needs to be derived and prepended
  678.          *    - blank or missing realm becomes same as hostname.
  679.          *
  680.          *  "site.com:80"  --> "http://site.com"
  681.          *  "site.com:443" --> "https://site.com"
  682.          *  "site.com:123" --> Who knows! (So add both)
  683.          *
  684.          * Note: For HTTP logins, the hostname never contained a username
  685.          *       or password. EG "user@site.com:80" shouldn't ever happen.
  686.          *
  687.          * Note: Proxy logins are also stored in this format.
  688.          */
  689.         if (aLogin.hostname.indexOf("://") == -1) {
  690.             var oldHost = aLogin.hostname;
  691.  
  692.             // Check for a trailing port number, EG "site.com:80". If there's
  693.             // no port, it wasn't saved by the browser and is probably some
  694.             // arbitrary string picked by an extension.
  695.             if (!/:\d+$/.test(aLogin.hostname)) {
  696.                 this.log("2E upgrade: no port, skipping " + aLogin.hostname);
  697.                 return upgradedLogins;
  698.             }
  699.  
  700.             // Parse out "host:port".
  701.             try {
  702.                 // Small hack: Need a scheme for nsIURI, so just prepend http.
  703.                 // We'll check for a port == -1 in case nsIURI ever starts
  704.                 // noticing that "http://foo:80" is using the default port.
  705.                 var uri = this._ioService.newURI("http://" + aLogin.hostname,
  706.                                                  null, null);
  707.                 var host = uri.host;
  708.                 var port = uri.port;
  709.             } catch (e) {
  710.                 this.log("2E upgrade: Can't parse hostname " + aLogin.hostname);
  711.                 return upgradedLogins;
  712.             }
  713.  
  714.             if (port == 80 || port == -1)
  715.                 aLogin.hostname = "http://" + host;
  716.             else if (port == 443)
  717.                 aLogin.hostname = "https://" + host;
  718.             else {
  719.                 // Not a standard port! Could be either http or https!
  720.                 // (Or maybe it's a proxy login!) To try and avoid
  721.                 // breaking logins, we'll add *both* http and https
  722.                 // versions.
  723.                 this.log("2E upgrade: Cloning login for " + aLogin.hostname);
  724.  
  725.                 aLogin.hostname = "http://" + host + ":" + port;
  726.  
  727.                 var extraLogin = new this._nsLoginInfo();
  728.                 extraLogin.init("https://" + host + ":" + port,
  729.                                 null, aLogin.httpRealm,
  730.                                 aLogin.username, aLogin.password, "", "");
  731.                 // We don't have decrypted values, unless we're importing from IE,
  732.                 // so clone the encrypted bits into the new entry.
  733.                 extraLogin.wrappedJSObject.encryptedPassword = 
  734.                     aLogin.wrappedJSObject.encryptedPassword;
  735.                 extraLogin.wrappedJSObject.encryptedUsername = 
  736.                     aLogin.wrappedJSObject.encryptedUsername;
  737.  
  738.                 if (extraLogin.httpRealm == "")
  739.                     extraLogin.httpRealm = extraLogin.hostname;
  740.                 
  741.                 upgradedLogins.push(extraLogin);
  742.             }
  743.  
  744.             // If the server didn't send a realm (or it was blank), we
  745.             // previously didn't store anything.
  746.             if (aLogin.httpRealm == "")
  747.                 aLogin.httpRealm = aLogin.hostname;
  748.  
  749.             this.log("2E upgrade: " + oldHost + " ---> " + aLogin.hostname);
  750.  
  751.             return upgradedLogins;
  752.         }
  753.  
  754.  
  755.         /*
  756.          * For form logins and non-HTTP channel logins (both were stored in
  757.          * the same format):
  758.          *
  759.          * Standardize URLs (.hostname and .actionURL)
  760.          *    - remove default port numbers, if specified
  761.          *      "http://site.com:80"  --> "http://site.com"
  762.          *    - remove usernames from URL (may move into aLogin.username)
  763.          *      "ftp://user@site.com" --> "ftp://site.com"
  764.          *
  765.          * Note: Passwords in the URL ("foo://user:pass@site.com") were not
  766.          *       stored in FF2, so no need to try to move the value into
  767.          *       aLogin.password.
  768.          */
  769.  
  770.         // closures in cleanupURL
  771.         var ioService = this._ioService;
  772.         var log = this.log;
  773.  
  774.         function cleanupURL(aURL, allowJS) {
  775.             var newURL, username = null, pathname = "";
  776.  
  777.             try {
  778.                 var uri = ioService.newURI(aURL, null, null);
  779.                 var scheme = uri.scheme;
  780.  
  781.                 if (allowJS && scheme == "javascript")
  782.                     return ["javascript:", null, ""];
  783.  
  784.                 newURL = scheme + "://" + uri.host;
  785.  
  786.                 // If the URL explicitly specified a port, only include it when
  787.                 // it's not the default. (We never want "http://foo.com:80")
  788.                 port = uri.port;
  789.                 if (port != -1) {
  790.                     var handler = ioService.getProtocolHandler(scheme);
  791.                     if (port != handler.defaultPort)
  792.                         newURL += ":" + port;
  793.                 }
  794.  
  795.                 // Could be a channel login with a username. 
  796.                 if (scheme != "http" && scheme != "https" && uri.username)
  797.                     username = uri.username;
  798.  
  799.                 if (uri.path != "/")
  800.                     pathname = uri.path;
  801.  
  802.             } catch (e) {
  803.                 log("Can't cleanup URL: " + aURL + " e: " + e);
  804.                 newURL = aURL;
  805.             }
  806.  
  807.             if (newURL != aURL)
  808.                 log("2E upgrade: " + aURL + " ---> " + newURL);
  809.  
  810.             return [newURL, username, pathname];
  811.         }
  812.  
  813.         const isMailNews = /^(ldaps?|smtp|imap|news|mailbox):\/\//;
  814.  
  815.         // Old mailnews logins were protocol logins with a username/password
  816.         // field name set.
  817.         var isFormLogin = (aLogin.formSubmitURL ||
  818.                            aLogin.usernameField ||
  819.                            aLogin.passwordField) &&
  820.                           !isMailNews.test(aLogin.hostname);
  821.  
  822.         var [hostname, username, pathname] = cleanupURL(aLogin.hostname);
  823.         aLogin.hostname = hostname;
  824.  
  825.         // If a non-HTTP URL contained a username, it wasn't stored in the
  826.         // encrypted username field (which contains an encrypted empty value)
  827.         // (Don't do this if it's a form login, though.)
  828.         if (username && !isFormLogin) {
  829.             if (isMailNews.test(aLogin.hostname))
  830.                 username = decodeURIComponent(username);
  831.  
  832.             var [encUsername, userCanceled] = this._encrypt(username);
  833.             if (!userCanceled)
  834.                 aLogin.wrappedJSObject.encryptedUsername = encUsername;
  835.         }
  836.  
  837.  
  838.         if (aLogin.formSubmitURL) {
  839.             [hostname, username, pathname] = cleanupURL(aLogin.formSubmitURL,
  840.                                                         true);
  841.             aLogin.formSubmitURL = hostname;
  842.             // username, if any, ignored.
  843.         }
  844.  
  845.  
  846.         /*
  847.          * For logins stored from non-HTTP channels
  848.          *    - Set httpRealm so they don't look like form logins
  849.          *     "ftp://site.com" --> "ftp://site.com (ftp://site.com)"
  850.          *
  851.          * Tricky: Form logins and non-HTTP channel logins are stored in the
  852.          * same format, and we don't want to add a realm to a form login.
  853.          * Form logins have field names, so only update the realm if there are
  854.          * no field names set. [Any login with a http[s]:// hostname is always
  855.          * a form login, so explicitly ignore those just to be safe.]
  856.          */
  857.         const isHTTP = /^https?:\/\//;
  858.         const isLDAP = /^ldaps?:\/\//;
  859.         const isNews = /^news?:\/\//;
  860.         if (!isHTTP.test(aLogin.hostname) && !isFormLogin) {
  861.             // LDAP and News logins need to keep the path.
  862.             if (isLDAP.test(aLogin.hostname) ||
  863.                 isNews.test(aLogin.hostname))
  864.                 aLogin.httpRealm = aLogin.hostname + pathname;
  865.             else
  866.                 aLogin.httpRealm = aLogin.hostname;
  867.  
  868.             aLogin.formSubmitURL = null;
  869.  
  870.             // Null out the form items because mailnews will no longer treat
  871.             // or expect these as form logins
  872.             if (isMailNews.test(aLogin.hostname)) {
  873.                 aLogin.usernameField = "";
  874.                 aLogin.passwordField = "";
  875.             }
  876.  
  877.             this.log("2E upgrade: set empty realm to " + aLogin.httpRealm);
  878.         }
  879.  
  880.         return upgradedLogins;
  881.     },
  882.  
  883.  
  884.     /*
  885.      * _readFile
  886.      *
  887.      */
  888.     _readFile : function () {
  889.         var formatVersion;
  890.  
  891.         this.log("Reading passwords from " + this._signonsFile.path);
  892.  
  893.         // If it doesn't exist, just bail out.
  894.         if (!this._signonsFile.exists()) {
  895.             this.log("No existing signons file found.");
  896.             return;
  897.         }
  898.  
  899.         var inputStream = Cc["@mozilla.org/network/file-input-stream;1"].
  900.                           createInstance(Ci.nsIFileInputStream);
  901.         // init the stream as RD_ONLY, -1 == default permissions.
  902.         inputStream.init(this._signonsFile, 0x01, -1, null);
  903.         var lineStream = inputStream.QueryInterface(Ci.nsILineInputStream);
  904.         var line = { value: "" };
  905.  
  906.         const STATE = { HEADER : 0, REJECT : 1, REALM : 2,
  907.                         USERFIELD : 3, USERVALUE : 4,
  908.                         PASSFIELD : 5, PASSVALUE : 6, ACTIONURL : 7,
  909.                         FILLER : 8 };
  910.         var parseState = STATE.HEADER;
  911.  
  912.         var processEntry = false;
  913.         var discardEntry = false;
  914.  
  915.         do {
  916.             var hasMore = lineStream.readLine(line);
  917.             try {
  918.               line.value = this._utfConverter.ConvertToUnicode(line.value);
  919.             } catch (e) {
  920.               this.log("Bad UTF8 conversion: " + line.value);
  921.               this._utfConverterReset();
  922.             }
  923.  
  924.             switch (parseState) {
  925.                 // Check file header
  926.                 case STATE.HEADER:
  927.                     if (line.value == "#2c") {
  928.                         formatVersion = 0x2c;
  929.                     } else if (line.value == "#2d") {
  930.                         formatVersion = 0x2d;
  931.                     } else if (line.value == "#2e") {
  932.                         formatVersion = 0x2e;
  933.                     } else {
  934.                         this.log("invalid file header (" + line.value + ")");
  935.                         throw "invalid file header in signons file";
  936.                         // We could disable later writing to file, so we
  937.                         // don't clobber whatever it is. ...however, that
  938.                         // would mean corrupt files are not self-healing.
  939.                         return;
  940.                     }
  941.                     parseState++;
  942.                     break;
  943.  
  944.                 // Line is a hostname for which passwords should never be saved.
  945.                 case STATE.REJECT:
  946.                     if (line.value == ".") {
  947.                         parseState++;
  948.                         break;
  949.                     }
  950.  
  951.                     this._disabledHosts[line.value] = true;
  952.  
  953.                     break;
  954.  
  955.                 // Line is a hostname, saved login(s) will follow
  956.                 case STATE.REALM:
  957.                     var hostrealm = line.value;
  958.  
  959.                     // Format is "http://site.com", with "(some realm)"
  960.                     // appended if it's a HTTP-Auth login.
  961.                     const realmFormat = /^(.+?)( \(.*\))?$/;
  962.                     var matches = realmFormat.exec(hostrealm);
  963.                     var hostname, httpRealm;
  964.                     if (matches && matches.length == 3) {
  965.                         hostname  = matches[1];
  966.                         httpRealm = matches[2] ?
  967.                                         matches[2].slice(2, -1) : null;
  968.                     } else {
  969.                         if (hostrealm != "") {
  970.                             // Uhoh. This shouldn't happen, but try to deal.
  971.                             this.log("Error parsing host/realm: " + hostrealm);
  972.                         }
  973.                         hostname = hostrealm;
  974.                         httpRealm = null;
  975.                     }
  976.  
  977.                     parseState++;
  978.                     break;
  979.  
  980.                 // Line is the HTML 'name' attribute for the username field
  981.                 // (or "." to indicate end of hostrealm)
  982.                 case STATE.USERFIELD:
  983.                     if (line.value == ".") {
  984.                         discardEntry = false;
  985.                         parseState = STATE.REALM;
  986.                         break;
  987.                     }
  988.  
  989.                     // If we're discarding the entry, keep looping in this
  990.                     // state until we hit the "." marking the end of the entry.
  991.                     if (discardEntry)
  992.                         break;
  993.  
  994.                     var entry = new this._nsLoginInfo();
  995.                     entry.hostname  = hostname;
  996.                     entry.httpRealm = httpRealm;
  997.  
  998.                     entry.usernameField = line.value;
  999.                     parseState++;
  1000.                     break;
  1001.  
  1002.                 // Line is a username
  1003.                 case STATE.USERVALUE:
  1004.                     entry.wrappedJSObject.encryptedUsername = line.value;
  1005.                     parseState++;
  1006.                     break;
  1007.  
  1008.                 // Line is the HTML 'name' attribute for the password field,
  1009.                 // with a leading '*' character
  1010.                 case STATE.PASSFIELD:
  1011.                     if (line.value.charAt(0) != '*') {
  1012.                         discardEntry = true;
  1013.                         entry = null;
  1014.                         parseState = STATE.USERFIELD;
  1015.                         break;
  1016.                     }
  1017.                     entry.passwordField = line.value.substr(1);
  1018.                     parseState++;
  1019.                     break;
  1020.  
  1021.                 // Line is a password
  1022.                 case STATE.PASSVALUE:
  1023.                     entry.wrappedJSObject.encryptedPassword = line.value;
  1024.  
  1025.                     // Version 2C doesn't have an ACTIONURL  line, so
  1026.                     // process entry now.
  1027.                     if (formatVersion < 0x2d)
  1028.                         processEntry = true;
  1029.  
  1030.                     parseState++;
  1031.                     break;
  1032.  
  1033.                 // Line is the action URL
  1034.                 case STATE.ACTIONURL:
  1035.                     var formSubmitURL = line.value;
  1036.                     if (!formSubmitURL && entry.httpRealm != null)
  1037.                         entry.formSubmitURL = null;
  1038.                     else
  1039.                         entry.formSubmitURL = formSubmitURL;
  1040.  
  1041.                     // Version 2D doesn't have a FILLER line, so
  1042.                     // process entry now.
  1043.                     if (formatVersion < 0x2e)
  1044.                         processEntry = true;
  1045.  
  1046.                     parseState++;
  1047.                     break;
  1048.  
  1049.                 // Line is unused filler for future use
  1050.                 case STATE.FILLER:
  1051.                     // Save the line's value (so we can dump it back out when
  1052.                     // we save the file next time) for forwards compatability.
  1053.                     entry.wrappedJSObject.filler = line.value;
  1054.                     processEntry = true;
  1055.  
  1056.                     parseState++;
  1057.                     break;
  1058.             }
  1059.  
  1060.             // If we've read all the lines for the current entry,
  1061.             // process it and reset the parse state for the next entry.
  1062.             if (processEntry) {
  1063.                 if (formatVersion < 0x2d) {
  1064.                     // A blank, non-null value is handled as a wildcard.
  1065.                     if (entry.httpRealm != null)
  1066.                         entry.formSubmitURL = null;
  1067.                     else
  1068.                         entry.formSubmitURL = "";
  1069.                 }
  1070.  
  1071.                 // Upgrading an entry to 2E can sometimes result in the need
  1072.                 // to create an extra login.
  1073.                 var entries = [entry];
  1074.                 if (formatVersion < 0x2e)
  1075.                     entries = this._upgrade_entry_to_2E(entry);
  1076.  
  1077.  
  1078.                 for each (var e in entries) {
  1079.                     if (!this._logins[e.hostname])
  1080.                         this._logins[e.hostname] = [];
  1081.                     this._logins[e.hostname].push(e);
  1082.                 }
  1083.  
  1084.                 entry = null;
  1085.                 processEntry = false;
  1086.                 parseState = STATE.USERFIELD;
  1087.             }
  1088.         } while (hasMore);
  1089.  
  1090.         lineStream.close();
  1091.  
  1092.         return;
  1093.     },
  1094.  
  1095.  
  1096.     /*
  1097.      * _writeFile
  1098.      *
  1099.      * Returns true if the operation was successfully completed, or false
  1100.      * if there was an error (probably the user refusing to enter a
  1101.      * master password if prompted).
  1102.      */
  1103.     _writeFile : function () {
  1104.         var converter = this._utfConverter;
  1105.         function writeLine(data) {
  1106.             data = converter.ConvertFromUnicode(data);
  1107.             data += converter.Finish();
  1108.             data += "\r\n";
  1109.             outputStream.write(data, data.length);
  1110.         }
  1111.  
  1112.         this.log("Writing passwords to " + this._signonsFile.path);
  1113.  
  1114.         var safeStream = Cc["@mozilla.org/network/safe-file-output-stream;1"].
  1115.                          createInstance(Ci.nsIFileOutputStream);
  1116.         // WR_ONLY|CREAT|TRUNC
  1117.         safeStream.init(this._signonsFile, 0x02 | 0x08 | 0x20, 0600, null);
  1118.  
  1119.         var outputStream = Cc["@mozilla.org/network/buffered-output-stream;1"].
  1120.                            createInstance(Ci.nsIBufferedOutputStream);
  1121.         outputStream.init(safeStream, 8192);
  1122.         outputStream.QueryInterface(Ci.nsISafeOutputStream); // for .finish()
  1123.  
  1124.  
  1125.         // write file version header
  1126.         writeLine("#2e");
  1127.  
  1128.         // write disabled logins list
  1129.         for (var hostname in this._disabledHosts) {
  1130.             writeLine(hostname);
  1131.         }
  1132.  
  1133.         // write end-of-reject-list marker
  1134.         writeLine(".");
  1135.  
  1136.         for (var hostname in this._logins) {
  1137.             function sortByRealm(a,b) {
  1138.                 a = a.httpRealm;
  1139.                 b = b.httpRealm;
  1140.  
  1141.                 if (!a && !b)
  1142.                     return  0;
  1143.  
  1144.                 if (!a || a < b)
  1145.                     return -1;
  1146.  
  1147.                 if (!b || b > a)
  1148.                     return  1;
  1149.  
  1150.                 return 0; // a==b, neither is null
  1151.             }
  1152.  
  1153.             // Sort logins by httpRealm. This allows us to group multiple
  1154.             // logins for the same realm together.
  1155.             this._logins[hostname].sort(sortByRealm);
  1156.  
  1157.  
  1158.             // write each login known for the host
  1159.             var lastRealm = null;
  1160.             var firstEntry = true;
  1161.             var userCanceled = false;
  1162.             for each (var login in this._logins[hostname]) {
  1163.  
  1164.                 // If this login is for a new realm, start a new entry.
  1165.                 if (login.httpRealm != lastRealm || firstEntry) {
  1166.                     // end previous entry, if needed.
  1167.                     if (!firstEntry)
  1168.                         writeLine(".");
  1169.  
  1170.                     var hostrealm = login.hostname;
  1171.                     if (login.httpRealm)
  1172.                         hostrealm += " (" + login.httpRealm + ")";
  1173.  
  1174.                     writeLine(hostrealm);
  1175.                 }
  1176.  
  1177.                 firstEntry = false;
  1178.  
  1179.                 // Get the encrypted value of the username. Newly added
  1180.                 // logins will need the plaintext value encrypted.
  1181.                 var encUsername = login.wrappedJSObject.encryptedUsername;
  1182.                 if (!encUsername) {
  1183.                     [encUsername, userCanceled] = this._encrypt(login.username);
  1184.                     login.wrappedJSObject.encryptedUsername = encUsername;
  1185.                 }
  1186.  
  1187.                 if (userCanceled)
  1188.                     break;
  1189.  
  1190.                 // Get the encrypted value of the password. Newly added
  1191.                 // logins will need the plaintext value encrypted.
  1192.                 var encPassword = login.wrappedJSObject.encryptedPassword;
  1193.                 if (!encPassword) {
  1194.                     [encPassword, userCanceled] = this._encrypt(login.password);
  1195.                     login.wrappedJSObject.encryptedPassword = encPassword;
  1196.                 }
  1197.  
  1198.                 if (userCanceled)
  1199.                     break;
  1200.  
  1201.  
  1202.                 writeLine((login.usernameField ?  login.usernameField : ""));
  1203.                 writeLine(encUsername);
  1204.                 writeLine("*" +
  1205.                     (login.passwordField ?  login.passwordField : ""));
  1206.                 writeLine(encPassword);
  1207.                 writeLine((login.formSubmitURL ? login.formSubmitURL : ""));
  1208.                 if (login.wrappedJSObject.filler)
  1209.                     writeLine(login.wrappedJSObject.filler);
  1210.                 else
  1211.                     writeLine("---");
  1212.  
  1213.                 lastRealm = login.httpRealm;
  1214.             }
  1215.  
  1216.             if (userCanceled) {
  1217.                 this.log("User canceled Master Password, aborting write.");
  1218.                 // .close will cause an abort w/o modifying original file
  1219.                 outputStream.close();
  1220.                 return false;
  1221.             }
  1222.  
  1223.             // write end-of-host marker
  1224.             writeLine(".");
  1225.         }
  1226.  
  1227.         // [if there were no hosts, no end-of-host marker (".") needed]
  1228.  
  1229.         outputStream.finish();
  1230.         return true;
  1231.     },
  1232.  
  1233.  
  1234.     /*
  1235.      * _decryptLogins
  1236.      *
  1237.      * Decrypts username and password fields in the provided array of
  1238.      * logins. This is deferred from the _readFile() code, so that
  1239.      * the user is not prompted for a master password (if set) until
  1240.      * the entries are actually used.
  1241.      *
  1242.      * The entries specified by the array will be decrypted, if possible.
  1243.      * An array of successfully decrypted logins will be returned. The return
  1244.      * value should be given to external callers (since still-encrypted
  1245.      * entries are useless), whereas internal callers generally don't want
  1246.      * to lose unencrypted entries (eg, because the user clicked Cancel
  1247.      * instead of entering their master password)
  1248.      */
  1249.     _decryptLogins : function (logins) {
  1250.         var result = [], userCanceled = false;
  1251.  
  1252.         for each (var login in logins) {
  1253.             var decryptedUsername, decryptedPassword;
  1254.  
  1255.             [decryptedUsername, userCanceled] =
  1256.                 this._decrypt(login.wrappedJSObject.encryptedUsername);
  1257.  
  1258.             if (userCanceled)
  1259.                 break;
  1260.  
  1261.             [decryptedPassword, userCanceled] =
  1262.                 this._decrypt(login.wrappedJSObject.encryptedPassword);
  1263.  
  1264.             // Probably can't hit this case, but for completeness...
  1265.             if (userCanceled)
  1266.                 break;
  1267.  
  1268.             // If decryption failed (corrupt entry?) skip it. Note that we
  1269.             // allow password-only logins, so decryptedUsername can be "".
  1270.             if (decryptedUsername == null || !decryptedPassword)
  1271.                 continue;
  1272.  
  1273.             // Return copies to the caller. Prevents callers from modifying
  1274.             // our internal stoage, and helps avoid keeping decrypted data in
  1275.             // memory (although this is fuzzy, because of GC issues).
  1276.             var clone = new this._nsLoginInfo();
  1277.             clone.init(login.hostname, login.formSubmitURL, login.httpRealm,
  1278.                        decryptedUsername, decryptedPassword,
  1279.                        login.usernameField, login.passwordField);
  1280.  
  1281.             // Old mime64-obscured entries should be opportunistically
  1282.             // reencrypted in the new format.
  1283.             var recrypted;
  1284.             if (login.wrappedJSObject.encryptedUsername &&
  1285.                 login.wrappedJSObject.encryptedUsername.charAt(0) == '~') {
  1286.                   [recrypted, userCanceled] = this._encrypt(decryptedUsername);
  1287.  
  1288.                   if (userCanceled)
  1289.                     break;
  1290.  
  1291.                   login.wrappedJSObject.encryptedUsername = recrypted;
  1292.             }
  1293.  
  1294.             if (login.wrappedJSObject.encryptedPassword &&
  1295.                 login.wrappedJSObject.encryptedPassword.charAt(0) == '~') {
  1296.                   [recrypted, userCanceled] = this._encrypt(decryptedPassword);
  1297.  
  1298.                   if (userCanceled)
  1299.                     break;
  1300.  
  1301.                   login.wrappedJSObject.encryptedPassword = recrypted;
  1302.             }
  1303.  
  1304.             result.push(clone);
  1305.         }
  1306.  
  1307.         return [result, userCanceled];
  1308.     },
  1309.  
  1310.  
  1311.     /*
  1312.      * _encrypt
  1313.      *
  1314.      * Encrypts the specified string, using the SecretDecoderRing.
  1315.      *
  1316.      * Returns [cipherText, userCanceled] where:
  1317.      *  cipherText   -- the encrypted string, or null if it failed.
  1318.      *  userCanceled -- if the encryption failed, this is true if the
  1319.      *                  user selected Cancel when prompted to enter their
  1320.      *                  Master Password. The caller should bail out, and not
  1321.      *                  not request that more things be encrypted (which 
  1322.      *                  results in prompting the user for a Master Password
  1323.      *                  over and over.)
  1324.      */
  1325.     _encrypt : function (plainText) {
  1326.         var cipherText = null, userCanceled = false;
  1327.  
  1328.         try {
  1329.             var plainOctet = this._utfConverter.ConvertFromUnicode(plainText);
  1330.             plainOctet += this._utfConverter.Finish();
  1331.             cipherText = this._decoderRing.encryptString(plainOctet);
  1332.         } catch (e) {
  1333.             this.log("Failed to encrypt string. (" + e.name + ")");
  1334.             // If the user clicks Cancel, we get NS_ERROR_FAILURE.
  1335.             // (unlike decrypting, which gets NS_ERROR_NOT_AVAILABLE).
  1336.             if (e.result == Components.results.NS_ERROR_FAILURE)
  1337.                 userCanceled = true;
  1338.         }
  1339.  
  1340.         return [cipherText, userCanceled];
  1341.     },
  1342.  
  1343.  
  1344.     /*
  1345.      * _decrypt
  1346.      *
  1347.      * Decrypts the specified string, using the SecretDecoderRing.
  1348.      *
  1349.      * Returns [plainText, userCanceled] where:
  1350.      *  plainText    -- the decrypted string, or null if it failed.
  1351.      *  userCanceled -- if the decryption failed, this is true if the
  1352.      *                  user selected Cancel when prompted to enter their
  1353.      *                  Master Password. The caller should bail out, and not
  1354.      *                  not request that more things be decrypted (which 
  1355.      *                  results in prompting the user for a Master Password
  1356.      *                  over and over.)
  1357.      */
  1358.     _decrypt : function (cipherText) {
  1359.         var plainText = null, userCanceled = false;
  1360.  
  1361.         try {
  1362.             var plainOctet;
  1363.             if (cipherText.charAt(0) == '~') {
  1364.                 // The older file format obscured entries by
  1365.                 // base64-encoding them. These entries are signaled by a
  1366.                 // leading '~' character. 
  1367.                 plainOctet = atob(cipherText.substring(1));
  1368.             } else {
  1369.                 plainOctet = this._decoderRing.decryptString(cipherText);
  1370.             }
  1371.             plainText = this._utfConverter.ConvertToUnicode(plainOctet);
  1372.         } catch (e) {
  1373.             this.log("Failed to decrypt string: " + cipherText +
  1374.                 " (" + e.name + ")");
  1375.  
  1376.             // In the unlikely event the converter threw, reset it.
  1377.             this._utfConverterReset();
  1378.  
  1379.             // If the user clicks Cancel, we get NS_ERROR_NOT_AVAILABLE.
  1380.             // If the cipherText is bad / wrong key, we get NS_ERROR_FAILURE
  1381.             // Wrong passwords are handled by the decoderRing reprompting;
  1382.             // we get no notification.
  1383.             if (e.result == Components.results.NS_ERROR_NOT_AVAILABLE)
  1384.                 userCanceled = true;
  1385.         }
  1386.  
  1387.         return [plainText, userCanceled];
  1388.     },
  1389.  
  1390.  
  1391.  
  1392.  
  1393.     /* ================== nsILoginManagerIEMigratorHelper ================== */
  1394.  
  1395.  
  1396.  
  1397.  
  1398.     _migrationLoginManager : null,
  1399.  
  1400.     /*
  1401.      * migrateAndAddLogin
  1402.      *
  1403.      * Given a login with IE6-formatted fields, migrates it to the new format
  1404.      * and adds it to the login manager.
  1405.      *
  1406.      * Experimentally derived format of IE6 logins, see:
  1407.      *     https://bugzilla.mozilla.org/attachment.cgi?id=319346
  1408.      *
  1409.      * HTTP AUTH:
  1410.      * - hostname is always "example.com:123"
  1411.      *   * "example.com", "http://example.com", "http://example.com:80" all
  1412.      *     end up as just "example.com:80"
  1413.      *   * Entering "example.com:80" in the URL bar isn't recognized as a
  1414.      *     valid URL by IE6.
  1415.      *   * "https://example.com" is saved as "example.com:443"
  1416.      *   * "https://example.com:666" is saved as "example.com:666". Thus, for
  1417.      *     non-standard ports we don't know the right scheme, so create both.
  1418.      *
  1419.      * - an empty or missing "realm" in the WWW-Authenticate reply is stored
  1420.      *   as just an empty string by IE6.
  1421.      *
  1422.      * - IE6 will store logins where one or both (!) of the username/password
  1423.      *   is left blank. We don't support logins without a password, so these
  1424.      *   logins won't be added [addLogin() will throw].
  1425.      *
  1426.      * - IE6 won't recognize a URL with and embedded username/password (eg
  1427.      *   http://user@example.com, http://user:pass@example.com), so these
  1428.      *   shouldn't be encountered.
  1429.      *
  1430.      * - Our migration code doesn't extract non-HTTP logins (eg, FTP). So
  1431.      *   they shouldn't be encountered here. (Verified by saving FTP logins
  1432.      *   in IE and then importing in Firefox.)
  1433.      *
  1434.      *
  1435.      * FORM LOGINS:
  1436.      * - hostname is "http://site.com" or "https://site.com".
  1437.      *   * scheme always included
  1438.      *   * default port not included
  1439.      * - port numbers, even for non-standard posts, are never present!
  1440.      *   unfortunately, this means logins will only work on the default
  1441.      *   port, because we don't know what the original was (or even that
  1442.      *   it wasn't originally stored for the original port).
  1443.      * - Logins are stored without a field name by IE, but we look one up
  1444.      *   in the migrator for the username. The password field name will
  1445.      *   always be empty-string.
  1446.      */
  1447.     migrateAndAddLogin : function (aLogin) {
  1448.         // Initialize outself on the first call
  1449.         if (!this._migrationLoginManager) {
  1450.             // Connect to the correct preferences branch.
  1451.             this._prefBranch = Cc["@mozilla.org/preferences-service;1"].
  1452.                                getService(Ci.nsIPrefService);
  1453.             this._prefBranch = this._prefBranch.getBranch("signon.");
  1454.             this._prefBranch.QueryInterface(Ci.nsIPrefBranch2);
  1455.  
  1456.             this._debug = this._prefBranch.getBoolPref("debug");
  1457.  
  1458.             this._migrationLoginManager = Cc["@mozilla.org/login-manager;1"].
  1459.                                           getService(Ci.nsILoginManager);
  1460.         }
  1461.  
  1462.         this.log("Migrating login for " + aLogin.hostname);
  1463.  
  1464.         // The IE login is in the same format as the old password
  1465.         // manager entries, so just reuse that code.
  1466.         var logins = this._upgrade_entry_to_2E(aLogin);
  1467.  
  1468.         // Add logins via the login manager (and not this.addLogin),
  1469.         // lest an alternative storage module be in use.
  1470.         for each (var login in logins)
  1471.             this._migrationLoginManager.addLogin(login);
  1472.     }
  1473. }; // end of nsLoginManagerStorage_legacy implementation
  1474.  
  1475. var component = [LoginManagerStorage_legacy];
  1476. function NSGetModule(compMgr, fileSpec) {
  1477.     return XPCOMUtils.generateModule(component);
  1478. }
  1479.